package org.unidata.mdm.rest.v1.data.service.restore;

import java.util.Date;
import java.util.Objects;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.unidata.mdm.data.context.RestoreRecordRequestContext;
import org.unidata.mdm.data.context.RestoreRelationsRequestContext;
import org.unidata.mdm.data.dto.RestoreRecordDTO;
import org.unidata.mdm.data.type.data.EtalonRecord;
import org.unidata.mdm.rest.system.ro.DetailedErrorResponseRO;
import org.unidata.mdm.rest.v1.data.converter.DataRecordEtalonConverter;
import org.unidata.mdm.rest.v1.data.ro.keys.LsnRO;
import org.unidata.mdm.rest.v1.data.ro.keys.RecordExternalIdRO;
import org.unidata.mdm.rest.v1.data.ro.records.DataRecordRO;
import org.unidata.mdm.rest.v1.data.ro.restore.RestorePeriodRequestRO;
import org.unidata.mdm.rest.v1.data.ro.restore.RestoreRecordRequestRO;
import org.unidata.mdm.rest.v1.data.ro.restore.RestoreResultRO;
import org.unidata.mdm.rest.v1.data.service.AbstractDataRestService;
import org.unidata.mdm.system.type.runtime.MeasurementContextName;
import org.unidata.mdm.system.type.runtime.MeasurementPoint;
import org.unidata.mdm.system.util.ConvertUtils;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;

/**
 * Restore record service
 *
 * @author Alexandr Serov
 * @since 16.10.2020
 **/
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
@Path("/restore")
@Tag(name = "restore")
public class RestoreRestService extends AbstractDataRestService {

    private static final String RESTORE_TAG = "restore";

    /**
     * Restore previously deleted period.
     */
    @POST
    @Path("/period")
    @Operation(description = "Restores period of a record by record ID and 'from' and 'to' boundaries.", responses = {
        @ApiResponse(content = @Content(schema = @Schema(implementation = RestoreResultRO.class)), responseCode = "200"),
        @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "400"),
        @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
    }, tags = RESTORE_TAG)
    public RestoreResultRO restorePeriod(RestorePeriodRequestRO req) {
        return executeRestorePeriod(req);
    }


    /**
     * Restore previously deleted record.
     */
    @POST
    @Path("/record")
    @Operation(description = "Restore record. Only a full record is accepted!", responses = {
        @ApiResponse(content = @Content(schema = @Schema(implementation = RestoreResultRO.class)), responseCode = "200"),
        @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "400"),
        @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
    }, tags = RESTORE_TAG)
    public RestoreResultRO restoreRecord(RestoreRecordRequestRO req) {
        return executeRestoreRecord(req);
    }

    private RestoreResultRO executeRestoreRecord(RestoreRecordRequestRO req) {
        Objects.requireNonNull(req, "Restore request can't be null");
        MeasurementPoint.init(MeasurementContextName.MEASURE_UI_RESTORE);
        MeasurementPoint.start();
        try {

            DataRecordRO data = req.getRecord();
            RecordExternalIdRO extId = Objects.isNull(data) ? null : data.getExternalId();
            LsnRO lsn = Objects.isNull(data) ? null : data.getLsn();

            RestoreRecordDTO restored = dataRecordsService.restore(RestoreRecordRequestContext.builder()
                .record(DataRecordEtalonConverter.from(data))
                .draftId(req.getDraftId())
                .etalonKey(req.getEtalonKey())
                .lsn(Objects.isNull(lsn) ? null : lsn.getLsn())
                .shard(Objects.isNull(lsn) ? null : lsn.getShard())
                .sourceSystem(Objects.isNull(extId) ? null : extId.getSourceSystem())
                .externalId(Objects.isNull(extId) ? null : extId.getExternalId())
                .modified(req.isModified())
                .build());

            RestoreResultRO result = new RestoreResultRO();
            result.setDetails(errorsToDetails(restored.getErrors()));
            EtalonRecord restoredEtalon = restored.getEtalon();
            if (restoredEtalon != null) {
                result.setRecord(DataRecordEtalonConverter.to(restoredEtalon, restored.getRecordKeys()));
            }
            return result;
        } finally {
            MeasurementPoint.stop();
        }
    }

    private RestoreResultRO executeRestorePeriod(RestorePeriodRequestRO req) {
        Objects.requireNonNull(req, "Restore request can't be null");
        MeasurementPoint.init(MeasurementContextName.MEASURE_UI_PERIOD_RESTORE);
        MeasurementPoint.start();
        try {
            Date relValidFrom = ConvertUtils.localDateTime2Date(req.getRelationValidFrom());
            Date relValidTo = ConvertUtils.localDateTime2Date(req.getRelationValidTo());
            boolean relPeriodRestore = relValidFrom != null && relValidTo != null;
            RestoreRecordDTO restored = dataRecordsService.restore(RestoreRecordRequestContext.builder()
                .etalonKey(req.getEtalonKey())
                .fragment(RestoreRelationsRequestContext.builder()
                    .applyToAll(true)
                    .periodRestore(relPeriodRestore)
                    .validFrom(relValidFrom)
                    .validTo(relValidTo)
                    .build())
                .validFrom(ConvertUtils.localDateTime2Date(req.getValidFrom()))
                .validTo(ConvertUtils.localDateTime2Date(req.getValidTo()))
                .draftId(req.getDraftId())
                .record(DataRecordEtalonConverter.from(req.getRecord()))
                .periodRestore(true)
                .build());
            RestoreResultRO result = new RestoreResultRO();
            result.setDetails(errorsToDetails(restored.getErrors()));
            EtalonRecord restoredEtalon = restored.getEtalon();
            if (restoredEtalon != null) {
                result.setRecord(DataRecordEtalonConverter.to(restoredEtalon, restored.getRecordKeys()));
            }
            return result;
        } finally {
            MeasurementPoint.stop();
        }
    }

}
